﻿using System.Collections.Generic;
using System.Linq;
using VendingMachineLib.Coins;
using VendingMachineLib.Products;

namespace VendingMachineLib
{
    public class VendingMachine
    {
        private readonly ProductCollection _products;
        private readonly CoinCollection _coinRepository;

        private readonly CoinCollection _coinsInserted = new CoinCollection();

        public VendingMachine(ProductCollection products, CoinCollection coinRepository)
        {
            // TODO need some null param tests here
            _products = products;
            _coinRepository = coinRepository;
        }

        public void InsertCoin(ICoin coin)
        {
            // TODO need some null param tests here
            _coinsInserted.Add(coin);
        }

        public T Purchase<T>(out IEnumerable<ICoin> change) where T : class, IProduct
        {
            // TODO need some null param tests here
            if (_products.Count<T>() == 0)
                throw new ProductNotAvailableException();

            var selection = _products.Peek<T>();
            
            var selectedProductValue = selection.Value;
            
            CheckForInsufficientFunds(selectedProductValue);

            var valueTendered = _coinsInserted.Sum(x => x.Value);
            var changeDue = valueTendered - selectedProductValue;

            var conjoinedCoinRepository = _coinRepository.Clone();
            conjoinedCoinRepository.AddRange(_coinsInserted);
            change = conjoinedCoinRepository.RemoveValue(changeDue);

            _coinRepository.Clear();
            _coinRepository.AddRange(conjoinedCoinRepository);
            _coinsInserted.Clear();
            
            _products.Pop<T>();

            return selection as T;
        }

        public IEnumerable<ICoin> RejectCoins()
        {
            var rejectedCoins = _coinsInserted.ToList(); // copy the list we're about to clear.
            _coinsInserted.Clear();
            return rejectedCoins;
        }

        private void CheckForInsufficientFunds(int selectedProductValue)
        {
            // TODO need some null param tests here
            var valueTendered = _coinsInserted.Sum(x => x.Value);
            var changeDue = valueTendered - selectedProductValue;
            if (changeDue < 0)
                throw new InsufficientFundsException();
        }

        internal int ValueOfChangeAvailable
        {
            get
            {
                return _coinRepository.Sum(x => x.Value);
            }
        }

        public int ValueOfInsertedCoins
        {
            get
            {
                return _coinsInserted.Sum(x => x.Value);
            }
        }

        public int ProductCount<T>() where T : class, IProduct
        {
            return _products.Count<T>();
        }
    }
}
